/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.classes;

import edu.isi.pegasus.common.util.Separator;
import java.util.*;
import java.io.IOException;
import java.io.Writer;
import java.io.Serializable;

/**
 * This is a base class which collects attributes that belong to 
 * <code>Transformation</code> and <code>Derivation</code>.<p>
 *
 * Note: this class has a natural ordering that may be inconsistent with
 * equals due to differing implementations. The equal method will take
 * object type and primary key triple into consideration, making extensive
 * null checks. The compareTo method compares the type and short ids of
 * the Definitions.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2079 $
 *
 * @see Transformation
 * @see Derivation */
public abstract class Definition extends VDL
  implements Comparable, Serializable
{
  // common constants for quick type access
  /**
   * This is the return value for {@link #getType()} from a
   * {@link Transformation}. 
   */
  public static final int TRANSFORMATION = 0x71077345; // shell oil

  /**
   * This is the return value for {@link #getType()} from a
   * {@link Derivation}. 
   */
  public static final int DERIVATION = 0xCAFEBABE;

  /**
   * This is an abstract function that must be implemented by
   * instantiable children, of which currently only exist
   * {@link Transformation} and {@link Derivation} siblings and derivatives. 
   *
   * @return the integer representing the concrete definition type of a 
   * instance. The value of -1 *might* be used to indicate an unknown
   * type.
   */
  abstract public int getType();

  // common attributes from child elements
  /**
   * Each transformation and derivation resides in a namespace. Mind
   * that namespaces are currently flat. If you need to impose any kind
   * of hierarchy, please do so yourself, e.g. use periods between
   * hierarchy intifiers. The namespace is part of the key identifying
   * a logical transformation. The default is <code>null</code>.
   *
   * @see Transformation
   * @see Derivation
   */
  private String m_namespace;

  /**
   * Each transformation and derivation can be identified by a name.
   * For a transformation, this is part of the logical transformation
   * name. Derivations can no longer be anonymous.
   *
   * @see Transformation
   * @see Derivation
   */
  private String m_name;

  /**
   * Each transformation and derivation has a version associated with
   * their definition. While a version number is highly recommended for
   * transformation, being part of the primary key triple into the
   * (future) transformation catalog, a derivation can remain without
   * version. The default is <code>null</code>.
   *
   * @see Transformation
   * @see Derivation
   */
  private String m_version;

  // AttributeGroup "DefinitionsAG"
  /**
   * Yong's knowledgebase approach needs this.
   */
  private String m_description;
  /**
   * Yong's knowledgebase approach needs this.
   */
  private String m_title;
  /**
   * Yong's knowledgebase approach needs this.
   */
  private String m_keyword;
  /**
   * Yong's knowledgebase approach needs this.
   */
  private String m_url;

  /**
   * ctor: empty
   */
  public Definition()
  {
    this.m_namespace = null;
    this.m_version = null;
  }

  /**
   * Convenience ctor: name the definition. The name is part of a
   * logical {@link Transformation}. Note that a {@link Derivation} may
   * remain anonymous. The namespace will be the default namespace, or
   * taken from the {@link Definitions}. The version remains unset.
   *
   * @param name is the name to be used for the defintion.
   */
  public Definition( String name )
  {
    this.m_namespace = null;
    this.m_version = null;
    this.m_name = name;
  }

  /**
   * Convenience ctor: name the definition. The name is part of a
   * logical {@link Transformation}. Note that a {@link Derivation} may
   * remain anonymous. The version remains unset.
   *
   * @param namespace is the namespace the name resides in.
   * @param name is the name to be used for the defintion.
   */
  public Definition( String namespace, String name )
  {
    this.m_name = name;
    this.m_namespace = namespace;
    this.m_version = null;
  }

  /**
   * Convenience ctor: name the definition. The name is part of a
   * logical {@link Transformation}. Note that a {@link Derivation} may
   * remain anonymous.
   *
   * @param namespace is the namespace the name resides in.
   * @param name is the name to be used for the defintion.
   * @param version is the version of this definition.
   */
  public Definition( String namespace, String name, String version )
  {
    this.m_name = name;
    this.m_namespace = namespace;
    this.m_version = version;
  }

  /**
   * Implementation of the {@link java.lang.Comparable} interface.
   * Compares this object with the specified object for order. Returns a
   * negative integer, zero, or a positive integer as this object is
   * less than, equal to, or greater than the specified object. The 
   * definitions are compared by their type, and by their short ids. 
   *
   * @param o is the object to be compared
   * @return a negative number, zero, or a positive number, if the 
   * object compared against is less than, equals or greater than
   * this object.
   * @exception ClassCastException if the specified object's type
   * prevents it from being compared to this Object. 
   */
  public int compareTo( Object o )
  {
    if ( o instanceof Definition ) {
      Definition d = (Definition) o;
      int diff = d.getType() - getType();  // order is important
      return ( diff != 0 ? diff : d.shortID().compareTo(this.shortID()) );
    } else {
      throw new ClassCastException( "object is not a Definition" );
    }
  }

  /**
   * Calculate a hash code value for the object to support hash tables.
   *
   * @return a hash code value for the object.
   */
  public int hashCode()
  {
    int result = m_namespace == null ? 0 : m_namespace.hashCode();
    result = ( result << 8 ) ^ ( m_name == null ? 0 : m_name.hashCode() );
    result = ( result << 8 ) ^ ( m_version == null ? 0 : m_version.hashCode() );
    return ( result ^ getType() );
  }

  /**
   * Accessor: match the primary key of a definition.
   * Note, this match is not wildcard capable. The type of the definitions
   * will also be checked. The primary key of a definition is the triple
   * namespace, name and version. This function is null-capable.
   *
   * @param type is the type identifier TRANSFORMATION or DERIVATION
   * @param namespace is the namespace
   * @param name is the name
   * @param version is the version
   *
   * @return true, if the primary keys match, false otherwise.
   */
  public boolean match( int type, String namespace, String name, String version )
  {
    return ( // check type
	     type == this.getType() &&
	     // check namespace
	     ( m_namespace == null && namespace == null ||
	       m_namespace != null && namespace != null && namespace.equals(m_namespace) ) &&
	     // check name
	     ( m_name != null && name != null && name.equals(m_name) ) &&
	     // check version string
	     ( m_version == null && version == null ||
	       m_version != null && version != null && version.equals(m_version) ) );
  }

  /**
   * Accessor: match primary keys of two Definitions.
   * Note, this match is not wildcard capable. The type of the definitions
   * will also be checked. The primary key of a definition is the triple
   * namespace, name and version. The equals function is null-capable.
   *
   * @param obj the reference object with which to compare.
   * @return true, if the primary keys match, false otherwise.
   */
  public boolean equals( Object obj )
  {
    // ward against null
    if ( obj == null ) return false;

    // shortcut
    if ( obj == this ) return true;

    // don't compare apples with oranges
    if ( ! (obj instanceof Definition) ) return false;

    // now we can safely cast
    Definition d = (Definition) obj;
    return match( d.getType(), d.getNamespace(), d.getName(), d.getVersion() );
  }

  /**
   * Accessor: Obtains the current description state.
   *
   * @return a string containing a descriptive remark on the definition,
   * or null for no description.
   * @see #setDescription(java.lang.String)
   */
  public String getDescription()
  { return this.m_description; }

  /**
   * Accessor: Obtains the current keyword state.
   *
   * @return a string containing a collection of keywords describing the
   * definition, or null for no keywords. 
   * @see #setKeyword(java.lang.String)
   */
  public String getKeyword()
  { return this.m_keyword; }

  /**
   * Accessor: Obtains the current name of the definition. Note that
   * a name is mandatory for any {@link Transformation}, but a 
   * {@link Derivation} may remain anonymous.
   *
   * @return the current name used for the definition. Note that derivations
   * may be anonymous. Returns null, if no name exists.
   * @see #setName(java.lang.String)
   */
  public String getName()
  { return this.m_name; }

  /**
   * Accessor: Obtains the current namespace that is used for the
   * definition. Note that a namespace is part of the key for any 
   * {@link Transformation}.
   *
   * @return the namespace the definition resides in, or null, if
   * no namespace was defined.
   * @see #setNamespace(java.lang.String)
   */
  public String getNamespace()
  { return this.m_namespace; }

  /**
   * Accessor: Obtains the current title state.
   *
   * @return the title given to this definition, or null, if there
   * was no title defined.
   * @see #setTitle(java.lang.String)
   */
  public String getTitle()
  { return this.m_title; }

  /**
   * Accessor: Obtains the current URI definition.
   *
   * @return the URL pointing to related information or a project, or
   * null, if no URL was registered.
   * @see #setUrl(java.lang.String)
   */
  public String getUrl()
  { return this.m_url; }

  /**
   * Accessor: Obtains the current version of the definition. A version
   * is an integral part of a logical {@link Transformation}.
   *
   * @return the version number of this definition, or null, if no
   * version number was defined.
   * @see #setVersion(java.lang.String)
   */
  public String getVersion()
  { return this.m_version; }

  /**
   * Accessor: Sets the description.
   *
   * @param description
   * @see #getDescription()
   */
  public void setDescription(String description)
  { this.m_description = description; }

  /**
   * Accessor: Sets or overwrites the keyword list.
   *
   * @param keyword
   * @see #getKeyword()
   */
  public void setKeyword(String keyword)
  { this.m_keyword = keyword; }

  /**
   * Accessor: Sets or overwrite the currently given name.
   *
   * @param name
   * @see #getName()
   */
  public void setName(String name)
  { this.m_name = name; }

  /**
   * Accessor: Sets or overwrites the namespace identifier.
   *
   * @param namespace
   * @see #getNamespace()
   */
  public void setNamespace(String namespace)
  { this.m_namespace = namespace; }

  /**
   * Accessor: Sets the current title for the definition.
   *
   * @param title
   * @see #getTitle()
   */
  public void setTitle(String title)
  { this.m_title = title; }

  /**
   * Accessor: Sets the project reference.
   *
   * @param url
   * @see #getUrl()
   */
  public void setUrl(String url)
  { this.m_url = url; }

  /**
   * Accessor: Sets the version of the definition.
   *
   * @param version
   * @see #getVersion()
   */
  public void setVersion(String version)
  { this.m_version = version; }

  /**
   * Identify the transformation or derivation by its name.
   */
  public abstract String identify();

  /**
   * Create the short id from ns:id:version.
   * @param d is a Definition, or null for non-siblings
   * @param namespace is the namespace to use, may be null.
   * @param name is the name to produce the id for, should not be null.
   * @param version is a version string, may be null.
   * @return A string which textually identifies a Definition.
   * @exception RuntimeException, if the name and definition are both null.
   */
  public static String shortID( Definition d, String namespace,
				String name, String version )
  {
    if ( name != null ) 
      return Separator.combine( namespace, name, version );
    else if ( d != null ) 
      return Separator.combine( namespace, Integer.toHexString(d.hashCode()), version );
    else 
      throw new RuntimeException("Definitions require valid identifiers" );
  }

  /** 
   * Constructs dynamically a short descriptive, hopefully unique
   * identifier for this derivation w/o referring to any transformation.
   * FIXME: Anonymous derivations get their hash code, which is well
   * for the first versions working without database. Later versions
   * with database must use some unique sequence mechanism instead.
   *
   * @return a string describing the derivation
   * @see Object#hashCode()
   */
  public String shortID()
  {
    return shortID( this, this.m_namespace, this.m_name, this.m_version );
  }

  /**
   * The toXML method is a partial method, to be incorporated/called
   * by its sibling class method of the same name. For this reason,
   * it does not fit the {@link VDL} interface.
   *
   * @return a string containing the attributes collected in the base class.
   */
  public String toXML()
  {
    StringBuffer result = new StringBuffer();

    if ( this.m_namespace != null ) 
      result.append(" namespace=\"").append(quote(this.m_namespace,true)).append("\"");

    if ( this.m_name != null ) 
      result.append(" name=\"").append(quote(this.m_name,true)).append("\"");

    if ( this.m_version != null ) 
      result.append(" version=\"").append(quote(this.m_version,true)).append("\"");

    if ( this.m_description != null ) 
      result.append(" description=\"").append(quote(this.m_description,true)).append("\"");

    if ( this.m_title != null ) 
      result.append(" title=\"").append(quote(this.m_title,true)).append("\"");

    if ( this.m_keyword != null ) 
      result.append(" keyword=\"").append(quote(this.m_keyword,true)).append("\"");

    if ( this.m_url != null ) 
      result.append(" url=\"").append(quote(this.m_url,true)).append("\"");
  
    return result.toString();
  }

  /**
   * The toXML method is a partial method, to be incorporated/called
   * by its sibling class method of the same name. For this reason,
   * it does not fit the {@link VDL} interface.
   *
   * @param stream is a stream opened and ready for writing. This can also
   * be a string stream for efficient output.
   * @exception IOException if something fishy happens to the stream.
   */
  public void toXML( Writer stream )
    throws IOException 
  {
    writeAttribute( stream, " namespace=\"", this.m_namespace );
    writeAttribute( stream, " name=\"", this.m_name );
    writeAttribute( stream, " version=\"", this.m_version );
    writeAttribute( stream, " description=\"", this.m_description );
    writeAttribute( stream, " title=\"", this.m_title );
    writeAttribute( stream, " keyword=\"", this.m_keyword );
    writeAttribute( stream, " url=\"", this.m_url );
  }
}
